// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using ValidationAspects;
using ValidationAspects.Sdk;

namespace TestValidationAspects
{
	[TestFixture]
	public class TestParameterValidationRegistry
	{
		[SetUp]
		public void SetUp()
		{
			TestHelpers.ResetFramework();
		}

		[Test]
		public void Test_FactoryDiscoveryFunc()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[0];

			Test.Throws(() => parameterInfo.ResolveValidator().Invoke(-1.0, new MockValidationContext()));
			parameterInfo.ResolveValidator().Invoke(100.0, new MockValidationContext());
		}

		#region Test ClearCache
		[Test]
		public void Test_ClearCache()
		{
			var parameterInfo = typeof(Dummy).GetMethod("NoDefValidationMethod").GetParameters()[0];
			parameterInfo.ReplaceValidation<double>((v, c) => { });

			Assert.IsNotNull(parameterInfo.ResolveValidator());

			ParameterValidationRegistry.ClearCache();

			Assert.IsNull(parameterInfo.ResolveValidator());
		}
		#endregion

		#region Test RevertValidation
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_RevertValidation_throws_for_null_target_param()
		{
			ParameterValidationRegistry.RevertValidation(null);
		}

		[Test]
		public void Test_RevertValidation_throws_for_out_parameter()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[2];

			Test.Throws<ValidationException>(() => parameterInfo.RevertValidation(),
			                                 string.Format("Validation cannot be applied to parameter [name:{0}] [method:{1}] [type:{2}]. Parameter must not declare the out modifier.", parameterInfo.Name,
			                                               parameterInfo.Member.Name, parameterInfo.Member.DeclaringType));
		}

		[Test]
		public void Test_RevertValidation()
		{
			// test for coverage
			typeof(Dummy).GetMethod("Method").GetParameters()[0].RevertValidation();
		}
		#endregion

		#region Test ReplaceValidation
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ReplaceValidation_throws_for_null_target_param()
		{
			ParameterValidationRegistry.ReplaceValidation(null, Enumerable.Empty<IValidatorFactory>());
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ReplaceValidation_throws_for_null_factories_param()
		{
			typeof(Dummy).GetMethod("Method").GetParameters()[0].ReplaceValidation(null);
		}

		[Test]
		public void Test_ReplaceValidation_throws_for_out_parameter()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[2];

			Test.Throws<ValidationException>(() => parameterInfo.ReplaceValidation(Enumerable.Empty<IValidatorFactory>()),
			                                 string.Format("Validation cannot be applied to parameter [name:{0}] [method:{1}] [type:{2}]. Parameter must not declare the out modifier.", parameterInfo.Name,
			                                               parameterInfo.Member.Name, parameterInfo.Member.DeclaringType));
		}

		[Test]
		public void Test_ReplaceValidation()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[0];

			MockValidatorFactory<double> factory = new MockValidatorFactory<double>();
			parameterInfo.ReplaceValidation(new[] {factory});

			IValidationContext context = new MockValidationContext();
			parameterInfo.ResolveValidator().Invoke(1.23, context);

			Assert.AreEqual(1.23, factory.Value);
			Assert.AreSame(context, factory.Context);
		}
		#endregion

		#region Test ReplaceValidationT
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ReplaceValidationT_throws_for_null_target_param()
		{
			ParameterValidationRegistry.ReplaceValidation<object>(null, (v, c) => { });
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ReplaceValidationT_throws_for_null_factories_param()
		{
			typeof(Dummy).GetMethod("Method").GetParameters()[0].ReplaceValidation<double>(null);
		}

		[Test]
		public void Test_ReplaceValidationT_throws_for_out_parameter()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[2];

			Test.Throws<ValidationException>(() => parameterInfo.ReplaceValidation<int>((v, c) => { }),
			                                 string.Format("Validation cannot be applied to parameter [name:{0}] [method:{1}] [type:{2}]. Parameter must not declare the out modifier.", parameterInfo.Name,
			                                               parameterInfo.Member.Name, parameterInfo.Member.DeclaringType));
		}

		[Test]
		public void Test_ReplaceValidationT()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[0];

			double valueFromValidator = 0;
			IValidationContext contextFromValidator = null;
			parameterInfo.ReplaceValidation<double>((v, c) =>
			                                        {
			                                        	valueFromValidator = v;
			                                        	contextFromValidator = c;
			                                        });

			IValidationContext context = new MockValidationContext();
			parameterInfo.ResolveValidator().Invoke(1.23, context);

			Assert.AreEqual(1.23, valueFromValidator);
			Assert.AreSame(context, contextFromValidator);
		}
		#endregion

		#region Test AddValidation
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_AddValidation_throws_for_null_target_param()
		{
			ParameterValidationRegistry.AddValidation(null, Enumerable.Empty<IValidatorFactory>());
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_AddValidation_throws_for_null_factories_param()
		{
			typeof(Dummy).GetMethod("Method").GetParameters()[0].AddValidation(null);
		}

		[Test]
		public void Test_AddValidation_throws_for_out_parameter()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[2];

			Test.Throws<ValidationException>(() => parameterInfo.AddValidation(Enumerable.Empty<IValidatorFactory>()),
			                                 string.Format("Validation cannot be applied to parameter [name:{0}] [method:{1}] [type:{2}]. Parameter must not declare the out modifier.", parameterInfo.Name,
			                                               parameterInfo.Member.Name, parameterInfo.Member.DeclaringType));
		}

		[Test]
		public void Test_AddValidation()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[0];

			MockValidatorFactory<double> factory = new MockValidatorFactory<double>();
			parameterInfo.AddValidation(new[] {factory});

			IValidationContext context = new MockValidationContext();
			parameterInfo.ResolveValidator().Invoke(1.23, context);

			Assert.AreEqual(1.23, factory.Value);
			Assert.AreSame(context, factory.Context);
		}
		#endregion

		#region Test AddValidationT
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_AddValidationT_throws_for_null_target_param()
		{
			ParameterValidationRegistry.AddValidation<object>(null, (v, c) => { });
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_AddValidationT_throws_for_null_factories_param()
		{
			typeof(Dummy).GetMethod("Method").GetParameters()[0].AddValidation<double>(null);
		}

		[Test]
		public void Test_AddValidationT_throws_for_out_parameter()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[2];

			Test.Throws<ValidationException>(() => parameterInfo.AddValidation<int>((v, c) => { }),
			                                 string.Format("Validation cannot be applied to parameter [name:{0}] [method:{1}] [type:{2}]. Parameter must not declare the out modifier.", parameterInfo.Name,
			                                               parameterInfo.Member.Name, parameterInfo.Member.DeclaringType));
		}

		[Test]
		public void Test_AddValidationT()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[0];

			double valueFromValidator = 0;
			IValidationContext contextFromValidator = null;
			parameterInfo.AddValidation<double>((v, c) =>
			                                    {
			                                    	valueFromValidator = v;
			                                    	contextFromValidator = c;
			                                    });

			IValidationContext context = new MockValidationContext();
			parameterInfo.ResolveValidator().Invoke(1.23, context);

			Assert.AreEqual(1.23, valueFromValidator);
			Assert.AreSame(context, contextFromValidator);
		}
		#endregion

		#region Test ResolveValidator
		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Test_ResolveValidator_throws_for_null_target_param()
		{
			ParameterValidationRegistry.ResolveValidator(null);
		}

		[Test]
		public void Test_ResolveValidator_throws_for_out_property()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[2];

			Test.Throws<ValidationException>(() => parameterInfo.ResolveValidator(),
			                                 string.Format("Validation cannot be applied to parameter [name:{0}] [method:{1}] [type:{2}]. Parameter must not declare the out modifier.", parameterInfo.Name,
			                                               parameterInfo.Member.Name, parameterInfo.Member.DeclaringType));
		}

		[Test]
		public void Test_ResolveValidator_throws_if_validator_construction_fails()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("InvalidValidatorDecoraction").GetParameters()[0];

			Test.Throws<ValidationException, ValidationException>(() => parameterInfo.ResolveValidator(),
			                                                      string.Format("Failed to construct validator for [type:{0}] [method:{1}] [parameter:{2}].", parameterInfo.Member.DeclaringType,
			                                                                    parameterInfo.Member.Name, parameterInfo.Name));
		}

		[Test]
		public void Test_ResolveValidator_returns_null_for_no_validators()
		{
			ParameterInfo parameterInfo = typeof(Dummy).GetMethod("Method").GetParameters()[0];
			parameterInfo.ReplaceValidation(Enumerable.Empty<IValidatorFactory>());
			Assert.IsNull(parameterInfo.ResolveValidator());
		}
		#endregion

		#region Mocks
		public class Dummy
		{
			public void Method([Minimum(0)] double d, [NotNullOrEmpty] string s, out int i)
			{
				i = 0;
			}

			public void InvalidValidatorDecoraction([Minimum(0)] string s) {}

			public void NoDefValidationMethod(double d) {}
		}
		#endregion
	}
}